package org.chipu.shared.controllers;

import java.awt.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.*;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.KeyStroke;
import org.chipu.shared.interfaces.Loggeable;

public abstract class Settings implements Loggeable {
	protected Logger logger = Logger.getLogger(getClass().getName());
	protected final Properties defaultConfig = new Properties();
	protected Properties conf = new Properties(defaultConfig);
	protected String imagePrefix;
	private boolean enableWarnings = true;

	protected Settings() {/**/}

	@Override
	public void setLogger(Logger logger) {
		if (logger != null)
			this.logger = logger;
	}

	// ----- AbstractBean

	private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

	public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
		pcs.addPropertyChangeListener(propertyName, listener);
	}

	protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
		pcs.firePropertyChange(propertyName, oldValue, newValue);
	}

	// -----

	/**
	 * @param prefix It is used when loading icons, so in the configuration file only the icon name is necessary
	 */
	public void setIconPrefix(String prefix) {
		imagePrefix = prefix == null? "": prefix;
	}

	public void restart() {
		conf.clear();
	}

	public void setEnableWarnings(boolean value) {
		boolean tmp = enableWarnings;
		enableWarnings = value;
		firePropertyChange("enablewarnings", tmp, value);
	}

	public boolean containsKey(String key) {
		return conf.containsKey(key) || defaultConfig.containsKey(key);
	}

	@SuppressWarnings("unchecked")
	public List<String> propertyNames() {
		return Collections.list((Enumeration<String>) conf.propertyNames());
	}

	/** Returns a copy of the configuration */
	public Properties getProperties() {
		Properties p = (Properties) defaultConfig.clone();
		for(Map.Entry<Object, Object> entry : conf.entrySet())
			p.put(entry.getKey(), entry.getValue());
		return p;
	}

	public boolean isDefault(String key, String value) {
		return defaultConfig.get(key) != null && defaultConfig.getProperty(key).equals(value);
	}

	protected boolean isDefault(String key, boolean value) {
		return defaultConfig.get(key) != null && Boolean.parseBoolean(defaultConfig.getProperty(key)) == value;
	}

	@Override
	public String toString() {
		return getClass().getSimpleName() + ": " + defaultConfig + "\n\t" + conf;
	}

	// ---------- Getters

	public String get(String key) {
		return get(key, true);
	}

	public String get(String key, boolean showWarnings) {
		if (enableWarnings && showWarnings && !containsKey(key)) {
			logger.warning("Key not found: " + key);
			if (logger != Logger.getLogger(getClass().getName()))// test
				Thread.dumpStack();
		}
		String value = conf.getProperty(key);
		if (value != null)
			// FIXME do it when saving/retrieving
			value = conf.getProperty(key).replace("\\\\", "\\");
		return value;
	}

	public String[] getStrings(String key, String delim, boolean showWarnings) {
		// return get(key).split(delim);
		try {
			StringTokenizer str = delim == null? new StringTokenizer(conf.getProperty(key)): new StringTokenizer(
				conf.getProperty(key), delim);
			String[] s = new String[str.countTokens()];
			int i = 0;
			while(str.hasMoreTokens())
				s[i++] = str.nextToken().trim();
			return s;
		} catch(NullPointerException ex) {
			if (enableWarnings && showWarnings) {
				logger.warning("Error retrieving strings (" + key + ":" + conf.getProperty(key) + ")");
				if (logger != Logger.getLogger(getClass().getName()))
					Thread.dumpStack();
			}
			return new String[0];
		}
	}

	public boolean getBoolean(String key) {
		return getBoolean(key, true);
	}

	public boolean getBoolean(String key, boolean showWarnings) {
		return getBoolean(key, showWarnings, false);
	}

	public boolean getBoolean(String key, boolean showWarnings, boolean def) {
		String s = get(key, false);
		if (s != null && ("true".equals(s) || "false".equals(s)))
			return Boolean.parseBoolean(s);
		if (enableWarnings && showWarnings)
			logger.warning("Tried to convert '" + s + "' to boolean, key: '" + key + '\'');
		return def;
	}

	public short getShort(String key, boolean showWarnings, short def) {
		try {
			return Short.parseShort(get(key, false));
		} catch(Exception e) {
			if (enableWarnings && showWarnings)
				logger.warning("Tried to convert '" + conf.getProperty(key) + "' to short, key: '" + key + '\'');
			return def;
		}
	}

	public int getInt(String key, boolean showWarnings, int def) {
		try {
			return Integer.parseInt(get(key, false));
		} catch(Exception e) {
			if (enableWarnings && showWarnings)
				logger.warning("Tried to convert '" + conf.getProperty(key) + "' to int, key: '" + key + '\'');
			return def;
		}
	}

	public long getLong(String key, boolean showWarnings, long def) {
		try {
			return Long.parseLong(get(key, false));
		} catch(Exception e) {
			if (enableWarnings && showWarnings)
				logger.warning("Tried to convert '" + conf.getProperty(key) + "' to long, key: '" + key + '\'');
			return def;
		}
	}

	public float getFloat(String key, boolean showWarnings, float def) {
		try {
			return Float.parseFloat(get(key, false));
		} catch(Exception e) {
			if (enableWarnings && showWarnings)
				logger.warning("Tried to convert '" + conf.getProperty(key) + "' to float, key: '" + key + '\'');
			return def;
		}
	}

	public double getDouble(String key, boolean showWarnings, double def) {
		try {
			return Double.parseDouble(get(key, false));
		} catch(Exception e) {
			if (enableWarnings && showWarnings)
				logger.warning("Tried to convert '" + conf.getProperty(key) + "' to double, key: '" + key + '\'');
			return def;
		}
	}

	public Color getColor(String key) {
		return getColor(key, true);
	}

	/**
	 * Better efficiency (2.5:1) than based on 3 properties.<br>
	 * The default is WHITE
	 */
	public Color getColor(String key, boolean showWarnings) {
		return getColor(key, showWarnings, Color.white);
	}

	public Color getColor(String key, boolean showWarnings, Color defaultValue) {
		try {
			return Color.decode(get(key, false));
		} catch(Exception ex) {
			if (enableWarnings && showWarnings)
				logger.warning("Error retrieving color (" + key + ":" + conf.getProperty(key) + ")");
			return defaultValue;
		}
	}

	public Color[] getColors(String key, boolean showWarnings) {
		try {
			String[] s = getStrings(key, null, showWarnings);
			Color[] COLORS = new Color[s.length];
			for(int i = 0; i < s.length; i++)
				COLORS[i] = Color.decode(s[i]);
			return COLORS;
		} catch(Exception ex) {
			if (enableWarnings && showWarnings)
				logger.warning("Error retrieving colors (" + key + ":" + conf.getProperty(key) + ")");
			return new Color[]{};
		}
	}

	/**
	 * Better efficiency (1.4:1) than based on 2 properties.<br>
	 * It may return null
	 */
	public Dimension getDimension(String key) {
		try {
			// SEE StringTokenizer str = new StringTokenizer(getString(propertyName));
			// return new Dimension(Integer.valueOf(str.nextToken()).intValue(), Integer.valueOf(
			// str.nextToken()).intValue());
			String s = conf.getProperty(key);
			int index = s.indexOf(' ');
			return new Dimension(Integer.valueOf(s.substring(0, index)).intValue(), Integer.valueOf(s.substring(index + 1))
				.intValue());
		} catch(Exception ex) {
			if (enableWarnings)
				logger.warning("Error retrieving dimension (" + key + ":" + conf.getProperty(key) + ")");
			return null;
		}
	}

	/** It may return null */
	public Rectangle getBounds(String key, boolean showWarnings) {
		try {
			if (conf.get(key) != null && conf.get(key) != "") {
				StringTokenizer str = new StringTokenizer(conf.getProperty(key));
				Rectangle r = new Rectangle(getPositiveInteger(str.nextToken()), getPositiveInteger(str.nextToken()),
					getPositiveInteger(str.nextToken()), getPositiveInteger(str.nextToken()));
				Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
				if (r.x + r.width <= d.width && r.y + r.height <= d.height)
					return r;
			}
		} catch(Exception ex) {
			if (enableWarnings && showWarnings)
				logger.warning("Error retrieving bounds (" + key + ":" + conf.getProperty(key) + ")");
		}
		return null;
	}

	private static int getPositiveInteger(String s) {
		return Math.max(0, Integer.parseInt(s));
	}

	public List<String> getList(String key) {
		return getList(key, null, true);
	}

	public List<String> getList(String key, String separator) {
		return getList(key, separator, true);
	}

	public List<String> getList(String key, String separator, boolean showWarnings) {
		return Arrays.asList(getStrings(key, separator, showWarnings));
	}

	public LinkedHashMap<String, String> getMap(String key) {
		return getMap(key, true);
	}

	/**
	 * Format in the config file:<br>
	 * key=value, key2=value2
	 */
	public LinkedHashMap<String, String> getMap(String key, boolean showWarnings) {
		LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
		for(String s : getStrings(key, ",", showWarnings)) {
			String[] ss = s.split("=");
			map.put(ss[0].trim(), ss[1].trim());
		}
		return map;
	}

	public List<Integer> getIntegerList(String key, String delim, boolean showWarnings) {
		String[] s = getStrings(key, delim, showWarnings);
		List<Integer> ints = new ArrayList<Integer>(s.length);
		for(String element : s)
			try {
				ints.add(new Integer(element));
			} catch(NumberFormatException ex) {
				if (enableWarnings)
					logger.log(Level.WARNING, "", ex);
			}
		return ints;
	}

	public List<Integer> getIntegerList(String key) {
		return getIntegerList(key, null, true);
	}

	public List<Integer> getIntegerList(String key, boolean showWarnings) {
		return getIntegerList(key, null, showWarnings);
	}

	/** The default style is PLAIN, the default size is 12 and the default family is Dialog */
	public Font getFont(String key) {
		return getFont(key, true);
	}

	/** The default style is PLAIN, the default size is 12 and the default family is Dialog */
	public Font getFont(String key, boolean showWarnings) {
		return Font.decode(get(key, showWarnings));
	}

	/** It may return null */
	public KeyStroke getKeystroke(String key) {
		return KeyStroke.getKeyStroke(conf.getProperty(key));
	}

	/** @param s Is the path or the name */
	protected String getPath(String s) {
		return imagePrefix + s.substring(s.lastIndexOf('/') + 1);
	}
}